package com.example.nettyclient.netty;

import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import com.example.nettyclient.netty.handler.NettyClientHandler;
import com.example.nettyclient.netty.listener.MessageStateListener;
import com.example.nettyclient.netty.listener.NettyClientListener;
import com.example.nettyclient.netty.status.ConnectState;

import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.sctp.nio.NioSctpChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;

/**
 * Created by littleGreens on 2018-11-10.
 * TCP 客户端
 */
public class NettyTcpClient  {

    private static final String TAG = "NettyTcpClient";//用于Log

    private EventLoopGroup group;//客户端只需要1个group

    private NettyClientListener listener;//面向接口编程，这里是mainactivity

    private Channel channel;//channelFuture.channel()，Netty 里面的IO操作全部是异步的。
                            // 这意味着，IO操作会立即返回，但是在调用结束时，无法保证IO操作已完成。
                            // 取而代之，将会返回给你一个ChannelFuture 实例，提供IO操作的结果信息或状态。

    private boolean isConnect = false;

    private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;//最大重连次数

    private int reconnectNum = MAX_CONNECT_TIMES;//重连计数

    private boolean isNeedReconnect = true;//是否需要重连
    private boolean isConnecting = false;//连接状态

    private long reconnectIntervalTime = 5000;//重连间隔时间
    private static final Integer CONNECT_TIMEOUT_MILLIS = 5000;

    private String host;//服务器地址
    private int tcp_port;//端口号
    private int mIndex;//客户端标识.(因为可能存在多个tcp连接)

    private long heartBeatInterval = 5;//心跳间隔时间，单位秒
    private boolean isSendheartBeat = false;//是否发送心跳
    private Object heartBeatData;//心跳数据，可以是String类型，也可以是byte[].

    private String packetSeparator;//用特殊字符，作为分隔符，解决粘包问题，默认是用换行符作为分隔符
    private int maxPacketLong = 1024;//最大长度

    private void setPacketSeparator(String separator) {
        this.packetSeparator = separator;
    }

    private void setMaxPacketLong(int maxPacketLong) {
        this.maxPacketLong = maxPacketLong;
    }

    private NettyTcpClient(String host, int tcp_port, int index) {//注意是private
        this.host = host;
        this.tcp_port = tcp_port;
        this.mIndex = index;
    }

    public int getMaxConnectTimes() {
        return MAX_CONNECT_TIMES;
    }

    public long getReconnectIntervalTime() {
        return reconnectIntervalTime;
    }

    public String getHost() {
        return host;
    }

    public int getTcp_port() {
        return tcp_port;
    }

    public int getIndex() {
        return mIndex;
    }

    public long getHeartBeatInterval() {
        return heartBeatInterval;
    }

    public boolean isSendheartBeat() {
        return isSendheartBeat;
    }


    /**
     * 连接服务器
     */
    public void connect() {
        if (isConnecting) {
            return;
        }
        //通过线程来实现
        Thread clientThread = new Thread("client-Netty") {
            @Override
            public void run() {
                super.run();
                isNeedReconnect = true;
                reconnectNum = MAX_CONNECT_TIMES;
                connectServer();
            }
        };
        clientThread.start();
    }

    /**
     * 连接服务器的具体操作
     */
    private void connectServer() {
        //同步
        synchronized (NettyTcpClient.this) {
            ChannelFuture channelFuture = null;
            if (!isConnect) {
                isConnecting = true;
                group = new NioEventLoopGroup(1);
                Bootstrap bootstrap = new Bootstrap().group(group)
                        .option(ChannelOption.TCP_NODELAY, true) //禁用nagle算法,Nagle算法试图减少TCP包的数量和结构性开销,
                                                                        // 将多个较小的包组合成较大的包进行发送.但这不是重点,
                                                                        // 关键是这个算法受TCP延迟确认影响, 会导致相继两次向连接发送请求包,
                                                                        // 读数据时会有一个最多达500毫秒的延时.
                        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)  //如果不设置超时，连接会一直占用本地线程，端口，
                                                                                    // 连接客户端一多，会导致本地端口用尽及CPU压力
                        .channel(NioSocketChannel.class)//NioSocketChannel 异步客户端TCP socket连接
                                                        //NioServerSocketChannel.class 异步服务器端TCP socket连接
                                                        //NioDatagramChannel.class 异步UDP连接
                                                        //NioSctpChannel.class 异步客户端Sctp连接
                                                        //NioServerSocketChannel.class 异步服务器端Sctp连接
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                if (isSendheartBeat) {//心跳机制
                                    ch.pipeline().addLast("ping",
                                            new IdleStateHandler(0, heartBeatInterval, 0, TimeUnit.SECONDS));//5s未发送数据，回调userEventTriggered
                                }

                                //黏包处理,需要客户端、服务端配合
                                //Android提供了TextUtils这个类来帮助我们处理较简单的字符串变换工作
                                if (!TextUtils.isEmpty(packetSeparator)) {
                                    ByteBuf delimiter= Unpooled.buffer();
                                    delimiter.writeBytes(packetSeparator.getBytes());
                                    /**
                                     * netty粘包处理
                                     * netty提供了多种编码器用于处理半包，这些编码器包含
                                     *
                                     * LineBasedFrameDecoder 时间解码器
                                     * DelimiterBasedFrameDecoder 分隔符解码器
                                     * FixedLengthFrameDecoder 定长解码器
                                     */
                                    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(maxPacketLong,delimiter));
                                } else {
                                    /**
                                     * LineBasedFrameDecoder 和LineEncoder采用的通信协议非常简单，
                                     * 即按照行进行分割，遇到一个换行符，则认为是一个完整的报文。
                                     * 在发送方，使用LineEncoder为数据添加换行符；
                                     * 在接受方，使用LineBasedFrameDecoder对换行符进行解码
                                     */
                                    ch.pipeline().addLast(new LineBasedFrameDecoder(maxPacketLong));
                                }
                                ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                                ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));


                                ch.pipeline().addLast(new NettyClientHandler(listener, mIndex, isSendheartBeat, heartBeatData,packetSeparator));
                            }
                        });

                try {
                    channelFuture = bootstrap.connect(host, tcp_port).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture channelFuture) throws Exception {
                            if (channelFuture.isSuccess()) {
                                Log.e(TAG, "连接成功");
                                reconnectNum = MAX_CONNECT_TIMES;
                                isConnect = true;
                                listener.onClientConnectState(true);
                                channel = channelFuture.channel();
                            } else {
                                Log.e(TAG, "连接失败");
                                isConnect = false;
                                listener.onClientConnectState(false);
                            }
                            isConnecting = false;
                        }
                    }).sync();

                    // Wait until the connection is closed.
                    channelFuture.channel().closeFuture().sync();
                    Log.e(TAG, " 断开连接");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    isConnect = false;
                    listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_CLOSED, mIndex);
                    if (null != channelFuture) {
                        if (channelFuture.channel() != null && channelFuture.channel().isOpen()) {
                            channelFuture.channel().close();
                        }
                    }
                    group.shutdownGracefully();
                    reconnect();
                }
            }
        }
    }


    public void disconnect() {
        Log.e(TAG, "disconnect");
        isNeedReconnect = false;
        group.shutdownGracefully();
    }

    public void reconnect() {
        Log.e(TAG, "reconnect");
        if (isNeedReconnect && reconnectNum > 0 && !isConnect) {
            reconnectNum--;
            SystemClock.sleep(reconnectIntervalTime);
            if (isNeedReconnect && reconnectNum > 0 && !isConnect) {
                Log.e(TAG, "重新连接");
                connectServer();
            }
        }
    }

    /**
     * 异步发送
     *
     * @param data 要发送的数据
     * @param listener 发送结果回调
     * @return 方法执行结果
     */
    public boolean sendMsgToServer(String data, final MessageStateListener listener) {
        boolean flag = channel != null && isConnect;
        if (flag) {
            String separator = TextUtils.isEmpty(packetSeparator) ? System.getProperty("line.separator") : packetSeparator;
            ChannelFuture channelFuture = channel.writeAndFlush(data + separator).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    listener.isSendSuccss(channelFuture.isSuccess());
                }
            });
        }
        return flag;
    }

    /**
     * 同步发送
     *
     * @param data 要发送的数据
     * @return 方法执行结果
     */
    public boolean sendMsgToServer(String data) {
        boolean flag = channel != null && isConnect;
        if (flag) {
            String separator = TextUtils.isEmpty(packetSeparator) ? System.getProperty("line.separator") : packetSeparator;
            ChannelFuture channelFuture = channel.writeAndFlush(data + separator).awaitUninterruptibly();
            return channelFuture.isSuccess();
        }
        return false;
    }


    public boolean sendMsgToServer(byte[] data, final MessageStateListener listener) {
        boolean flag = channel != null && isConnect;
        if (flag) {
            ByteBuf buf = Unpooled.copiedBuffer(data);
            channel.writeAndFlush(buf).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    listener.isSendSuccss(channelFuture.isSuccess());
                }
            });
        }
        return flag;
    }

    /**
     * 获取TCP连接状态
     *
     * @return  获取TCP连接状态
     */
    public boolean getConnectStatus() {
        return isConnect;
    }

    public boolean isConnecting() {
        return isConnecting;
    }

    public void setConnectStatus(boolean status) {
        this.isConnect = status;
    }



    public void setListener(NettyClientListener listener) {
        this.listener = listener;
    }

    public byte[] strToByteArray(String str) {
        if (str == null) {
            return null;
        }
        byte[] byteArray = str.getBytes();
        return byteArray;

    }

    /**
     * 构建者，创建NettyTcpClient
     */
    public static class Builder {

        /**
         * 最大重连次数
         */
        private int MAX_CONNECT_TIMES = Integer.MAX_VALUE;

        /**
         * 重连间隔
         */
        private long reconnectIntervalTime = 5000;
        /**
         * 服务器地址
         */
        private String host;
        /**
         * 服务器端口
         */
        private int tcp_port;
        /**
         * 客户端标识，(因为可能存在多个连接)
         */
        private int mIndex;

        /**
         * 是否发送心跳
         */
        private boolean isSendheartBeat;
        /**
         * 心跳时间间隔
         */
        private long heartBeatInterval = 5;

        /**
         * 心跳数据，可以是String类型，也可以是byte[].
         */
        private Object heartBeatData;

        private String packetSeparator;
        private int maxPacketLong = 1024;

        public Builder() {
            this.maxPacketLong = 1024;
        }


        public Builder setPacketSeparator(String packetSeparator) {
            this.packetSeparator = packetSeparator;
            return this;
        }

        public Builder setMaxPacketLong(int maxPacketLong) {
            this.maxPacketLong = maxPacketLong;
            return this;
        }

        public Builder setMaxReconnectTimes(int reConnectTimes) {
            this.MAX_CONNECT_TIMES = reConnectTimes;
            return this;
        }


        public Builder setReconnectIntervalTime(long reconnectIntervalTime) {
            this.reconnectIntervalTime = reconnectIntervalTime;
            return this;
        }


        public Builder setHost(String host) {
            this.host = host;
            return this;
        }

        public Builder setTcpPort(int tcp_port) {
            this.tcp_port = tcp_port;
            return this;
        }

        public Builder setIndex(int mIndex) {
            this.mIndex = mIndex;
            return this;
        }

        public Builder setHeartBeatInterval(long intervalTime) {
            this.heartBeatInterval = intervalTime;
            return this;
        }

        public Builder setSendheartBeat(boolean isSendheartBeat) {
            this.isSendheartBeat = isSendheartBeat;
            return this;
        }

        public Builder setHeartBeatData(Object heartBeatData) {
            this.heartBeatData = heartBeatData;
            return this;
        }

        public NettyTcpClient build() {
            NettyTcpClient nettyTcpClient = new NettyTcpClient(host, tcp_port, mIndex);
            nettyTcpClient.MAX_CONNECT_TIMES = this.MAX_CONNECT_TIMES;
            nettyTcpClient.reconnectIntervalTime = this.reconnectIntervalTime;
            nettyTcpClient.heartBeatInterval = this.heartBeatInterval;
            nettyTcpClient.isSendheartBeat = this.isSendheartBeat;
            nettyTcpClient.heartBeatData = this.heartBeatData;
            nettyTcpClient.packetSeparator = this.packetSeparator;
            nettyTcpClient.maxPacketLong = this.maxPacketLong;
            return nettyTcpClient;
        }
    }
}
